<?php
#
# dmBridge: a data access framework for CONTENTdm(R)
#
# Copyright © 2009, 2010, 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * <p>A base form class from which all forms should inherit.</p>
 *
 * <p>The form creation & submission process is as follows:</p>
 *
 * <ol>
 * <li>Present form by calling individual form elements' getHTMLTag()
 * methods</li>
 * <li>init() is called</li>
 * <li>loadFromDataStore() is called</li>
 * <li>User submits form via HTTP POST</li>
 * <li>The form is saved to the session</li>
 * <li>validate() is called</li>
 * <li>If the form validates, doAction() is called; otherwise validate() will
 * throw a DMIllegalArgumentException</li>
 * <li>If doAction() returns, the form is erased from the session</li>
 * </ol>
 *
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
abstract class DMAbstractForm {

	/**
	 * @var string ALIAS_PTR_SEPARATOR Used in forms that use an
	 * alias-pointer combination as a key.
	 */
	const ALIAS_PTR_SEPARATOR = "|";

	/**
	 * @var string DATA_PREFIX String with which all form element names are
	 * prefixed
	 */
	const DATA_PREFIX = "dmForm";

	private static $captcha_operands = array();

	protected $is_valid = false;
	private $fields = array();

	/**
	 * @return Indexed array with integer values and length of 2
	 * @since 0.1
	 */
	public static function getCaptchaOperands() {
		if (count(self::$captcha_operands) < 2) {
			for ($i = 0; $i < 2; $i++) {
				self::$captcha_operands[$i] = rand(0, 10);
			}
		}
		return self::$captcha_operands;
	}

	public function __construct() {
		$this->init();
		$post = DMHTTPRequest::getCurrent()->getRepresentation()->getFormValue(
				DMAbstractForm::DATA_PREFIX);
		if (is_array($post)) {
			$this->loadFromPOSTData($post);
		} else {
			$this->loadFromDataStore();
		}
	}

	public function __wakeup() {
		$post = DMHTTPRequest::getCurrent()->getRepresentation()->getFormValue(
				DMAbstractForm::DATA_PREFIX);
		if (is_array($post)) {
			$this->loadFromPOSTData($post);
		}
	}

	/**
	 * Adds a field to the form.
	 *
	 * @param DMFormField field
	 * @since 0.4
	 */
	public function addField(DMFormField $field) {
		$field->setForm($this);
		$this->fields[] = $field;
	}

	/**
	 * @return Array of DMFormField objects
	 * @since 0.4
	 */
	public function getFields() {
		return $this->fields;
	}

	/**
	 * @param string name
	 * @return DMFormField, or null if a DMFormField with name of $name does
	 * not exist.
	 * @since 0.4
	 */
	public function getFieldByName($name) {
		foreach ($this->getFields() as $f) {
			if ($f->getName() == $name) {
				return $f;
			}
		}
		return null;
	}

	/**
	 * @param string name
	 * @return Array of all DMFormFields with name matching $name, or an empty
	 * array if none exist.
	 * @since 2.0
	 */
	public function getFieldsByName($name) {
		$fields = array();
		foreach ($this->getFields() as $f) {
			$bracket_pos = strpos($f->getName(), "[");
			if (($f->getName() == $name)
					|| substr($f->getName(), 0, $bracket_pos) == $name) {
				$fields[] = $f;
			}
		}
		return $fields;
	}

	/**
	 * Should instantiate all form fields and add them to the form.
	 * 
	 * @return void
	 */
	abstract protected function init();

	/**
	 * Override to populate the form with values from a persistent store, if
	 * necessary; otherwise ignore.
	 *
	 * @return void
	 */
	protected function loadFromDataStore() {
	}

	/**
	 * @param data Array of POST data; typically the return value of
	 * <code>DMHTTPRequest::getCurrent()->getRepresentation()->getFormValue(DMAbstractForm::DATA_PREFIX)</code>
	 * @since 0.4
	 */
	protected function loadFromPOSTData(array $data) {
		foreach ($data as $field => $value) {
			$matching_fields = $this->getFieldsByName($field);
			foreach ($matching_fields as $mf) {
				$mf->setUserData(true);
			}

			if (count($matching_fields) == 1) {
				$matching_fields[0]->setValue($value);
				if ($matching_fields[0] instanceof DMCheckboxField
						|| $matching_fields[0] instanceof DMRadioField) {
					$matching_fields[0]->setChecked(true);
				}
			} else if (count($matching_fields) > 1) {
				if (!is_array($value)) {
					$value = array($value);
				}
				for ($i = 0; $i < count($value); $i++) {
					foreach ($matching_fields as $mf) {
						if ($mf instanceof DMRadioField
								|| $mf instanceof DMCheckboxField) {
							if ($mf->getValue() == $value[$i]) {
								$mf->setChecked(true);
								break;
							}
						}
					}
					$matching_fields[$i]->setValue($value[$i]);
				} // for
			} // else if
		} // foreach
	}

	/**
	 * @param string name
	 * @param mixed value
	 * @since 2.0
	 */
	public function checkRadioWithNameAndValue($name, $value) {
		foreach ($this->getFieldsByName($name) as $f) {
			if (!$f instanceof DMRadioField) {
				continue;
			}
			$f->setChecked(($f->getValue() == $value));
		}
	}

	/**
	 * Calls <code>isValid()</code> on all fields.
	 *
	 * @return void
	 * @throws DMIllegalArgumentException if a form field does not validate
	 * @since 0.4
	 */
	public function validate() {
		foreach ($this->getFields() as $f) {
			if (!$f->isValid()) {
				throw new DMIllegalArgumentException(
					DMLocalizedString::getString("INVALID_FORM_DATA"));
			}
		}
		$this->is_valid = true;
	}

}
